package cn.virens.common.boot.dubbo.error;

import cn.virens.common.util.exception.APIException;
import cn.virens.common.util.exception.APIExceptionInter;
import cn.virens.common.util.exception.ExceptionUtil;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.service.GenericService;
import org.apache.dubbo.rpc.support.RpcUtils;
import org.dromara.hutool.core.text.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;

@Activate(group = CommonConstants.PROVIDER)
public class VirensExceptionFilter implements Filter, BaseFilter.Listener {
    @Nullable
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
            try {
                Throwable exception = appResponse.getException();

                // 打印异常信息
                this.logger.error(ExceptionUtil.m(exception), exception);

                // 获取异常处理SPI实现接口
                var defaultModule = ApplicationModel.defaultModel().getDefaultModule();
                var extensionLoader = defaultModule.getExtensionLoader(VirensExceptionHandler.class);
                for (VirensExceptionHandler handler : extensionLoader.getActivateExtensions()) {
                    if (handler == extensionLoader.getDefaultExtension()) {
                        continue;
                    }

                    var result = handler.exceptionHandling(exception);
                    if (result != null) {
                        appResponse.setException(result);

                        return;
                    }
                }

                // 将特定异常类型跳过异常处理
                if (exception instanceof APIExceptionInter) {
                    return;
                }
                if (exception instanceof RpcException) {
                    return;
                }
                if (exception instanceof Exception) {
                    return;
                }

                // 如果是申明的异常也放过处理
                try {
                    var parameterTypes = invocation.getParameterTypes();
                    var methodName = RpcUtils.getMethodName(invocation);
                    var method = invoker.getInterface().getMethod(methodName, parameterTypes);

                    for (Class<?> exceptionClass : method.getExceptionTypes()) {
                        if (exception.getClass().equals(exceptionClass)) {
                            return;
                        }
                    }
                } catch (NoSuchMethodException e) {
                    return;
                }

                // directly throw if exception class and interface class are in the same jar file.
                var serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                var exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                if (serviceFile != null && StrUtil.equals(exceptionFile, serviceFile)) {
                    return;
                }

                // directly throw if it's JDK exception
                String className = exception.getClass().getName();
                if (className.startsWith("java.") //
                        || className.startsWith("javax.") //
                        || className.startsWith("jakarta.")) {
                    return;
                }

                // 如果都不能处理成功
                var c = ExceptionUtil.getCode(exception);
                var m = ExceptionUtil.getMessage(exception);

                appResponse.setException(new APIException(c, m));
            } catch (Throwable e) {
                this.logger.error(ExceptionUtil.m(e), e);
            }
        }
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
        this.logger.error(ExceptionUtil.m(t), t);
    }
}
